<!DOCTYPE HTML>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
function promise_test(func, name, properties) {
    properties = properties || {};
    var test = async_test(name, properties);
    Promise.resolve(test.step(func, test, test))
           .then(function() { test.done(); })
           .catch(test.step_func(function(value) { throw value; }));
}

function structuredClone(o)
{
    return new Promise(function(resolve, reject) {
        var mc = new MessageChannel();
        mc.port2.onmessage = function(e) { resolve(e.data); };
        mc.port1.postMessage(o);
    });
}

promise_test(function() {
    var inner = {};
    var orig = { inner: inner };
    inner.outer = orig;
    return structuredClone(orig).then(function(clone) {
        assert_equals(clone.inner.outer, clone, 'Cycles should be preserved');
    });
}, 'Verify: "This algorithm preserves cycles..."');

promise_test(function() {
    var gen = {name: 'AES-CBC', length: 128};
    return crypto.subtle.generateKey(gen, false, ['encrypt']).then(function(key) {
        var simple = {};
        var blob = new Blob(['content']);
        var orig = {
            s1: simple, s2: simple,
            b1: blob, b2: blob,
            k1: key, k2: key
        };
        return structuredClone(orig).then(function(clone) {
            assert_equals(clone.s1, clone.s2, 'JS object identity should be preserved');
            assert_equals(clone.b1, clone.b2, 'Core object identity should be preserved');
            assert_equals(clone.k1, clone.k2, 'Module object identity should be preserved');
        });
    });
}, 'Verify: "This algorithm preserves... the identity of duplicate objects in graphs."');

promise_test(function() {
    var name = 'this is a setter';

    var orig = {};
    orig[name] = 'value';

    var setter_called = false;
    Object.defineProperty(Object.prototype, name, {
        set: function(a) {
            setter_called = true;
        }
    });

    assert_true(orig.hasOwnProperty(name));

    return structuredClone(orig).then(function(clone) {
        assert_equals(typeof clone, 'object', 'Clone should be an object');
        assert_false(setter_called, 'Setter should not be called by cloning algorithm.');
        assert_true(clone.hasOwnProperty(name), 'Cloning algorithm should add an own property.')
        assert_equals(clone[name], orig[name], 'Property value should match');
    });
}, 'Verify: "Add a new property..." (objects)');

promise_test(function() {
    var name = '123456';

    var orig = [];
    orig[name] = 'value';

    var setter_called = false;
    Object.defineProperty(Object.prototype, name, {
        set: function(a) {
            setter_called = true;
        }
    });

    assert_true(orig.hasOwnProperty(name));

    return structuredClone(orig).then(function(clone) {
        assert_true(Array.isArray(clone), 'Clone should be an Array');
        assert_false(setter_called, 'Setter should not be called by cloning algorithm.');
        assert_true(clone.hasOwnProperty(name), 'Cloning algorithm should add an own property.')
        assert_equals(clone[name], orig[name], 'Property value should match');
    });
}, 'Verify: "Add a new property..." (arrays)');

promise_test(function() {
    var name = '256';

    var orig = [];
    for (var i = 0; i < 256; ++i) {
        orig[i] = i;
    }

    orig[name] = 'value';

    var setter_called = false;
    Object.defineProperty(Object.prototype, name, {
        set: function(a) {
            setter_called = true;
        }
    });

    assert_true(orig.hasOwnProperty(name));

    return structuredClone(orig).then(function(clone) {
        assert_true(Array.isArray(clone), 'Clone should be an Array');
        assert_false(setter_called, 'Setter should not be called by cloning algorithm.');
        assert_true(clone.hasOwnProperty(name), 'Cloning algorithm should add an own property.')
        assert_equals(clone[name], orig[name], 'Property value should match');
    });
}, 'Verify: "Add a new property..." (dense arrays)');

promise_test(function() {
    var orig = {
        emptySet: new Set,
        set: new Set([1, 2, 3]),
        emptyMap: new Map,
        map: new Map([[1, 2], [3, 4]]),
    };
    return structuredClone(orig).then(function(clone) {
        assert_true(clone.emptySet instanceof Set, 'Clone should be a Set');
        assert_true(clone.emptyMap instanceof Map, 'Clone should be a Map');
        assert_true(clone.set instanceof Set, 'Clone should be a Set');
        assert_true(clone.map instanceof Map, 'Clone should be a Map');
        assert_equals(clone.emptySet.size, 0, 'Clone should be the same size');
        assert_equals(clone.emptyMap.size, 0, 'Clone should be the same size');
        assert_equals(clone.set.size, orig.set.size, 'Clone should be the same size');
        assert_equals(clone.map.size, orig.map.size, 'Clone should be the same size');
        assert_true(clone.set.has(1) && clone.set.has(2) && clone.set.has(3), 'Cloned set should have the same keys');
        assert_true(clone.map.get(1) == 2 && clone.map.get(3) == 4, 'Cloned map should have the same keys and values');
    });
}, 'Maps and Sets are cloned');

promise_test(function() {
    var set = new Set;
    set.add(set);
    var map = new Map;
    map.set(map, map);
    var orig = { map: map, set: set };
    return structuredClone(orig).then(function(clone) {
        assert_true(clone.set instanceof Set, 'Clone should be a Set');
        assert_true(clone.map instanceof Map, 'Clone should be a Map');
        assert_equals(clone.set, Array.from(clone.set)[0], 'Recursive sets should preserve identity');
        assert_equals(clone.map, Array.from(clone.map)[0][0], 'Recursive maps should preserve identity');
        assert_equals(clone.map, Array.from(clone.map)[0][1], 'Recursive maps should preserve identity');
    });
}, 'Cloning Maps and Sets preserve cycles');

promise_test(function() {
    var set = new Set;
    var map = new Map;
    var setMutator = {
        get val() {
            set.add('mutated');
            return 'setMutator';
        }
    }
    var mapMutator = {
        get val() {
            map.set('mutated', true);
            return 'mapMutator';
        }
    }
    set.add(setMutator);
    map.set('mapMutator', mapMutator);
    var orig = { map: map, set: set };
    return structuredClone(orig).then(function(clone) {
        assert_true(clone.set instanceof Set, 'Clone should be a Set');
        assert_true(clone.map instanceof Map, 'Clone should be a Map');
        assert_equals(orig.set.size, 2, 'Original set should have been mutated');
        assert_equals(orig.map.size, 2, 'Original map should have been mutated');
        assert_equals(clone.set.size, 1, 'Cloned set should not reflect mutation');
        assert_equals(clone.map.size, 1, 'Cloned map should not reflect mutation');
        assert_equals(Array.from(clone.set)[0].val, 'setMutator', 'Cloned set should contain getter return value');
        assert_equals(clone.map.get('mapMutator').val, 'mapMutator', 'Cloned map should contain getter return value');
    });
}, 'Cloned Maps and Sets do not reflect mutations that occur during cloning');

promise_test(function() {
    var map = new Map([['key', function(){}]]);
    return structuredClone(map).then(function(clone) {
        assert_unreached('Should have thrown an exception');
    }, function(ex) {
        assert_true(ex instanceof DOMException, 'Should throw a DOMException');
        assert_equals(ex.code, DOMException.DATA_CLONE_ERR, 'Should be a DataCloneError');
    });
}, 'Cloning Maps should fail if they contain non-cloneable things');

promise_test(function() {
    var set = new Set([function(){}]);
    return structuredClone(set).then(function(clone) {
        assert_unreached('Should have thrown an exception');
    }, function(ex) {
        assert_true(ex instanceof DOMException, 'Should throw a DOMException');
        assert_equals(ex.code, DOMException.DATA_CLONE_ERR, 'Should be a DataCloneError');
    });
}, 'Cloning Sets should fail if they contain non-cloneable things');

promise_test(function() {
    return structuredClone(Symbol('foo')).then(function(clone) {
        assert_unreached('Should have thrown an exception');
    }, function(ex) {
        assert_true(ex instanceof DOMException, 'Should throw a DOMException');
        assert_equals(ex.code, DOMException.DATA_CLONE_ERR, 'Should be a DataCloneError');
    });
}, 'Cloning Symbols should fail');

</script>
